Chris Pollett > Old Classes > CS174
( Print View )

Student Corner:
  [Submit Sec2]
  [Grades Sec2]

  [Submit Sec3]
  [Grades Sec3]

  [
Lecture Notes]
  [Discussion Board]

Course Info:
  [Texts & Links]
  [Description]
  [Course Outcomes]
  [Outcomes Matrix]
  [Course Schedule]
  [Grading]
  [Requirements/HW/Quizzes]
  [Class Protocols]
  [Exam Info]
  [Regrades]
  [University Policies]
  [Announcements]

HW Assignments:
  [Hw1]  [Hw2]  [Hw3]
  [Hw4]  [Hw5]  [Quizzes]

Practice Exams:
  [Midterm]  [Final]

                           












HW#4 --- last modified February 06 2019 04:10:42..

Solution set.

Due date: Nov 21

Files to be submitted:
  Hw4.zip

Purpose: To make use of a web service, to gain experience working with XML, JSON, JSONP.

Related Course Outcomes:

The main course outcomes covered by this assignment are:

LO2 -- Write schemas, DTDs, and style sheets for XML documents.

LO4 -- Write client-side scripts that validate HTML forms.

LO5 -- Develop and deploy web applications that involve components, web services, and database

Specification:

For this homework, you will create a web site called PasteChart where people can share their data as graphical charts. Before describing the project in detail, let's discuss the general set up in which you should code your project. Your application should be written using the same MVA architecture with namespaces as used in Hw3. For this homework, your application should be written so as to work with Composer and classes should be loaded using Composer's autoloader. Your composer.json file should list Simpletest (Simpletest on packagist) greater than or equal to version 1.1 as a dependency. When writing your program you should make use of git. If the grader does a git log and then does a git diff between successive commits. The difference should always be less than 150 lines. You should also break the coding of your project into issues and there should be a folder in your Hw4 folder, calles issues, in which you should have a one text file/issue. The issue files's filenames should follow the format: number_short_title_for_issue.txt. For example, 0001_Set_up_folder_structure.txt. In the text file there should be a title of the issue, who in your group its assigned to, and a short description of what needed to be done for the issue. This can be followed by any discussion back-and-forth on the issue.

Now let's move to the specifics of what you need to code and how the PasteChart site should work. When a user comes to the landing page of your site, the user should be greeted with a web page with the title and h1 heading PasteChart. Beneath this should be written, "Share your data in charts!" The rest of this page should consist of a text field with label, "Chart Title", then a large textarea under which is a Share button. The placeholder for the textarea should say the allowed format for the data. Be aware that placeholders and textarea can be tricky to get working together. The data format should be a comma separated list of values, one per line, up to 50 lines of at most 80 characters, representing points to be plotted. The first coordinate should represent a text label (can involve numbers but can't involve a comma), the remaining coordinates should represent values that correspond to that text label from up to 5 sources. For example, the text labels might have months of the year (Jan, Feb, etc) and the values could correspond to the rabbit and wolf populations during those months in thousands. In which case, rows of data entered into the textarea might look like:

Jan,600,5.4
Feb,450,5.0
... 
On every row, the first coordinate must be a nonempty string, however, for the remaining coordinates if a value is missing, we represent that with the empty string. For example:
Aug,,10.1

When the user clicks the Share button, your app should check if the data conforms to the format indicated above. This should be done both in the client before the form is submitted and on the server (because the server shouldn't trust the client). You should write at least a couple Simpletest unit tests to tests that the server side checking of the form data is behaving as expected. On the client, if the data doesn't conform, a message should be displayed and the form should not be submitted. On the server, if it doesn't conform, an error should briefly be displayed, and then the landing page with a cleaned version of the data on it should be drawn. If the data does conform to the format, the data should be md5 hashed using PHP's hash function and the tuple (md5, title, data) should be stored in a database table. Let XXXXX represent the characters in the md5 hash. Then your script should draw a page with title and h1 heading, "XXXXX LineGraph - PasteChart". Beneath this should be a line graph with title as given by the user, and using the user provided data. Define a constant BASE_URL in your config.php script as the url of your website. This page should then have the lines (in p or div or pre or other tags of your choice):

Share your chart and data at the URLs below:

As a LineGraph:
BASE_URL/?c=chart&a=show&arg1=LineGraph&arg2=XXXXX

As a PointGraph:
BASE_URL/?c=chart&a=show&arg1=PointGraph&arg2=XXXXX

As a Histogram:
BASE_URL/?c=chart&a=show&arg1=Histogram&arg2=XXXXX

As XML data:
BASE_URL/?c=chart&a=show&arg1=xml&arg2=XXXXX

As JSON data:
BASE_URL/?c=chart&a=show&arg1=json&arg2=XXXXX

As JSONP data:
BASE_URL/?c=chart&a=show&arg1=jsonp&arg2=XXXXX&arg3=javascript_callback

The url of this page should match that of the LineGraph url above. Each of the other urls above should work and produce a page like this one, except with title and h1 heading changed to the value of arg1, and the actual displayed graph or data being of type arg1. For the XML format, I want you to create your own XML language that you define in a file chart.dtd that you include in your Hw4 folder. The grader will validate your XML output against this DTD using Oxygen. Let YYYYY denote the JSON data output if arg1 was JSON, let arg3=foo in a corresponding jsonp url. Then the JSONP response should look like:

foo(YYYYY);

To draw graphs, I want you to enhance the Javascript code, chart.js, below to handle k-tuples for points rather than just 2-tuples for points, and I want you to enhance it so that it can draw histograms.

//chart.js
/**
 * Defines a class useful for making point and line graph charts.
 *
 * Example use:
 * graph = new Chart(some_html_element_id, 
 *     {"Jan":7, "Feb":20, "Dec":5}, {"title":"Test Chart - Month v Value"});
 * graph.draw();
 *
 * @param String chart_id id of tag that the chart will be drawn into
 * @param Object data a sequence {x_1:y_1, ... x_1,y_n} points to plot
 *    x_i's can be arbitrary labels, y_i's are assumes to be floats
 * @param Object (optional) properties override values for any of the
 *      properties listed in the property_defaults variable below
 */
function Chart(chart_id, data)
{
    var self = this;
    var p = Chart.prototype;
    var properties = (typeof arguments[2] !== 'undefined') ?
        arguments[2] : {};
    var container = document.getElementById(chart_id);
    if (!container) {
        return false;
    }
    var property_defaults = {
        'axes_color' : 'rgb(128,128,128)', // color of the x and y axes lines
        'caption' : '', // caption text appears at bottom
        'caption_style' : 'font-size: 14pt; text-align: center;',
            // CSS styles to apply to caption text
        'data_color' : 'rgb(0,0,255)', //color used to draw grah
        'height' : 500, //height of area to draw into in pixels
        'line_width' : 1, // width of line in line graph
        'x_padding' : 30, //x-distance left side of canvas tag to y-axis
        'y_padding' : 30, //y-distance bottom of canvas tag to x-axis
        'point_radius' : 3, //radius of points that are plot in point graph
        'tick_length' : 10, // length of tick marks along axes
        'ticks_y' : 5, // number of tick marks to use for the y axis
        'tick_font_size' : 10, //size of font to use when labeling ticks
        'title' : '', // title text appears at top
        'title_style' : 'font-size:24pt; text-align: center;',
            // CSS styles to apply to title text
        'type' : 'LineGraph', // currently, can be either a LineGraph or
            //PointGraph
        'width' : 500 //width of area to draw into in pixels
    };
    for (var property_key in property_defaults) {
        if (typeof properties[property_key] !== 'undefined') {
            this[property_key] = properties[property_key];
        } else {
            this[property_key] = property_defaults[property_key];
        }
    }
    title_tag = (this.title) ? '<div style="' + this.title_style
         + 'width:' + this.width + '" >' + this.title + '</div>' : '';
    caption_tag = (this.caption) ? '<figcaption style="' + this.caption_style
         + 'width:' + this.width + '" >' + this.caption + '</figcaption>' : '';
    container.innerHTML = '<figure>'+ title_tag + '<canvas id="' + chart_id +
        '-content" ></canvas>' + caption_tag + '</figure>';
    canvas = document.getElementById(chart_id + '-content');
    if (!canvas || typeof canvas.getContext === 'undefined') {
        return
    }
    var context = canvas.getContext("2d");
    canvas.width = this.width;
    canvas.height = this.height;
    this.data = data;
    /**
     * Main function used to draw the graph type selected
     */
    p.draw = function()
    {
        self['draw' + self.type]();
    }
    /**
     * Used to store in fields the min and max y values as well as the start
     * and end x keys, and the range = max_y - min_y
     */
    p.initMinMaxRange = function()
    {
        self.min_value = null;
        self.max_value = null;
        self.start;
        self.end;
        var key;
        for (key in data) {
            if (self.min_value === null) {
                self.min_value = data[key];
                self.max_value = data[key];
                self.start = key;
            }
            if (data[key] < self.min_value) {
                self.min_value = data[key];
            }
            if (data[key] > self.max_value) {
                self.max_value = data[key];
            }
        }
        self.end = key;
        self.range = self.max_value - self.min_value;
    }
    /**
     * Used to draw a point at location x,y in the canvas
     */
    p.plotPoint = function(x,y)
    {
        var c = context;
        c.beginPath();
        c.arc(x, y, self.point_radius, 0, 2 * Math.PI, true);
        c.fill();
    }
    /**
     * Draws the x and y axes for the chart as well as ticks marks and values
     */
    p.renderAxes = function()
    {
        var c = context;
        var height = self.height - self.y_padding;
        c.strokeStyle = self.axes_color;
        c.lineWidth = self.line_width;
        c.beginPath();
        c.moveTo(self.x_padding - self.tick_length,
            self.height - self.y_padding);
        c.lineTo(self.width - self.x_padding,  height);  // x axis
        c.stroke();
        c.beginPath();
        c.moveTo(self.x_padding, self.tick_length);
        c.lineTo(self.x_padding, self.height - self.y_padding +
            self.tick_length);  // y axis
        c.stroke();
        var spacing_y = self.range/self.ticks_y;
        height -= self.tick_length;
        var min_y = parseFloat(self.min_value);
        var max_y = parseFloat(self.max_value);
        var num_format = new Intl.NumberFormat("en-US",
            {"maximumFractionDigits":2});
        // Draw y ticks and values
        for (var val = min_y; val < max_y + spacing_y; val += spacing_y) {
            y = self.tick_length + height * 
                (1 - (val - self.min_value)/self.range);
            c.font = self.tick_font_size + "px serif";
            c.fillText(num_format.format(val), 0, y + self.tick_font_size/2,
                self.x_padding - self.tick_length);
            c.beginPath();
            c.moveTo(self.x_padding - self.tick_length, y);
            c.lineTo(self.x_padding, y);
            c.stroke();
        }
        // Draw x ticks and values
        var dx = (self.width - 2 * self.x_padding) /
            (Object.keys(data).length - 1);
        var x = self.x_padding;
        for (key in data) {
            c.font = self.tick_font_size + "px serif";
            c.fillText(key, x - self.tick_font_size/2 * (key.length - 0.5), 
                self.height - self.y_padding +  self.tick_length +
                self.tick_font_size, self.tick_font_size * (key.length - 0.5));
            c.beginPath();
            c.moveTo(x, self.height - self.y_padding + self.tick_length);
            c.lineTo(x, self.height - self.y_padding);
            c.stroke();
            x += dx;
        }
    }
    /**
     * Draws a chart consisting of just x-y plots of points in data.
     */
    p.drawPointGraph = function()
    {
        self.initMinMaxRange();
        self.renderAxes();
        var dx = (self.width - 2*self.x_padding) /
            (Object.keys(data).length - 1);
        var c = context;
        c.lineWidth = self.line_width;
        c.strokeStyle = self.data_color;
        c.fillStyle = self.data_color;
        var height = self.height - self.y_padding - self.tick_length;
        var x = self.x_padding;
        for (key in data) {
            y = self.tick_length + height *
                (1 - (data[key] - self.min_value)/self.range);
            self.plotPoint(x, y);
            x += dx;
        }
    }
    /**
     * Draws a chart consisting of x-y plots of points in data, each adjacent
     * point pairs connected by a line segment
     */
    p.drawLineGraph = function()
    {
        self.drawPointGraph();
        var c = context;
        c.beginPath();
        var x = self.x_padding;
        var dx =  (self.width - 2*self.x_padding) /
            (Object.keys(data).length - 1);
        var height = self.height - self.y_padding  - self.tick_length;
        c.moveTo(x, self.tick_length + height * (1 -
            (data[self.start] - self.min_value)/self.range));
        for (key in data) {
            y = self.tick_length + height * 
                (1 - (data[key] - self.min_value)/self.range);
            c.lineTo(x, y);
            x += dx;
        }
        c.stroke();
    }
}

This completes the description of the homework, below is a point break down.

Point Breakdown

Project subfolder and namespace organization as in Hw3. Readme.txt file should explain how to set up project as well as how to run simpletest unit tests1pt
Project developed using git, has commits (0.5pts) and issues as described (0.5pts)1pt
Project uses Composer's autoloader to load in classes (0.5pt), composer.json lists Simpletest 1.1 as a dependency (0.5pt)1pt
Landing page is title, h1 heading, and form as described1pt
Chart form data validated on client side using Javascript before sent to server0.5pt
Chart form data validated on server side (0.5pts), have at least two simpletest unit tests to test number of items in tuple and number of line and line length constraints (0.5pts)1pt
Mysql database used as described to store chart data from main landing page, and used to retrieve chart data when displaying charts or other data formats.1pt
chart.js enhanced to handle k-tuples (0.5pts) and extended to draw histograms (0.5pts)1pt
Six url links for a given data set work as described (0.25pts each)1.5pts
chart.dtd is a valid DTD and data output when xml url visited validates against it using Oxygen1pt
Total10pts